package com.nulldev.util.other;

import java.io.IOException;
import java.lang.reflect.Array;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.print.attribute.AttributeSet;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.nulldev.util.crypto.encryptAPI.manage.Encryptor;
import com.nulldev.util.error.ErrorCallback;
import com.nulldev.util.io.fastIO.FastIO;
import com.nulldev.util.java.ReturnableFunction;
import com.nulldev.util.java.swing.extSwing.lookandfeel.themes.Theme;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.net.inetplus.IInetAddress.DNSSource;
import com.nulldev.util.other.builders.StrBuilder;

public class ArrayUtils {

	public static class StringArrayUtils{

		public static boolean contains(String[] array, String toFind) {
			for (String local : array) {
				if (local.contains(toFind)) {
					return true;
				}
			}
			return false;
		}

		public static boolean containsE(String[] array, String toFind) {
			for (String local : array) {
				if (local.startsWith(toFind) || local.contains(toFind)) {
					return true;
				}
			}
			return false;
		}

		public static boolean contains(List<String> array, String toFind) {
			for (String local : array) {
				if (local.contains(toFind)) {
					return true;
				}
			}
			return false;
		}

		public static String addFrom(final String[] array, final int loc) {
			if (loc > array.length) {
				return "";
			}
			final StringBuilder out = new StringBuilder();
			for (int i = loc; i < array.length; i++) {
				if (i >= array.length - 1) {
					out.append(array[i]);
				}else {
					out.append(array[i] + " ");
				}
			}
			return out.toString();
		}
		
		public static String addFrom(final String[] array, final int loc, final char ac) {
			if (loc > array.length) {
				return "";
			}
			final StringBuilder out = new StringBuilder();
			for (int i = loc; i < array.length; i++) {
				if (i >= array.length - 1) {
					out.append(array[i]);
				}else {
					out.append(array[i] + ac);
				}
			}
			return out.toString();
		}
		
		public static String addTo(final String[] array, final int loc) {
			if (loc > array.length || loc < 0) {
				return "";
			}
			final StringBuilder out = new StringBuilder();
			for (int i = 0; i < array.length; i++) {
				if (i >= array.length - 1) {
					out.append(array[i]);
				}else {
					out.append(array[i] + " ");
				}
			}
			return out.toString();
		}

		public static String find(final String[] input, final String toFind) {
			for (final String i : input) {
				if (i.contains(toFind)) {
					return i;
				}
			}
			return "";
		}

		public static String at(final String[] line, final int loc, final String fallback) {
			try {
				return line[loc];
			}catch (Exception e) {
				return fallback;
			}
		}

		public static String toReadableString(final String[] tags) {
			final StringBuilder s0 = new StringBuilder();
			for (int i = 0; i < tags.length; i++) {
				final String string = tags[i];
				if (i == tags.length - 1) {
					s0.append(string);
				}else {
					s0.append(string + ", ");
				}
			}
			return s0.toString();
		}
		
		public static String toString(final String[] tags) {
			final StringBuilder s0 = new StringBuilder();
			for (int i = 0; i < tags.length; i++) {
				final String string = tags[i];
				s0.append(string);
			}
			return s0.toString();
		}
	}

	public static class CharacterArrayUtils{

		public static boolean containsE(List<Character> array, char toFind) {
			for (char local : array) {
				if (local == toFind) {
					return true;
				}else {
				}
			}
			return false;
		}

		public static boolean containsE(char[] array, char toFind) {
			for (char local : array) {
				if (local == toFind) {
					return true;
				}
			}
			return false;
		}

		public static String alphabet() {
			final String[] s = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "G", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"};
			final StringBuilder data = new StringBuilder();
			for (final String x : s) {
				data.append(x);
			}
			return data.toString();
		}

	}

	private static final Logger log = LoggerFactory.getLogger();
	public static final String[] EMPTY_STRING_ARRAY = new String[0];
	public static final char[] EMPTY_CHAR_ARRAY = new char[0];

    public static <T> T[] clone(final T[] array) {
        if (array == null) {
            return null;
        }
        return array.clone();
    }

    public static boolean contains(final Object[] array, final Object objectToFind) {
        return indexOf(array, objectToFind) != -1;
    }

    public static int indexOf(final Object[] array, final Object objectToFind) {
        return indexOf(array, objectToFind, 0);
    }

    public static int indexOf(final Object[] array, final Object objectToFind, int startIndex) {
        if (array == null) {
            return -1;
        }
        if (startIndex < 0) {
            startIndex = 0;
        }
        if (objectToFind == null) {
            for (int i = startIndex; i < array.length; ++i) {
                if (array[i] == null) {
                    return i;
                }
            }
        }
        else {
            for (int i = startIndex; i < array.length; ++i) {
                if (objectToFind.equals(array[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

	public static <T> T[] cloneArray(final T[] input) {
		return (T[]) input.clone();
	}

    public static String identityToString(final Object object) {
        if (object == null) {
            return null;
        }
        final StringBuilder builder = new StringBuilder();
        identityToString(builder, object);
        return builder.toString();
    }

    public static void identityToString(final Appendable appendable, final Object object) throws IOException {
        notNull(object, "Cannot get the toString of a null identity", new Object[0]);
        appendable.append(object.getClass().getName()).append('@').append(Integer.toHexString(System.identityHashCode(object)));
    }

    @Deprecated
    public static void identityToString(final StrBuilder builder, final Object object) {
        notNull(object, "Cannot get the toString of a null identity", new Object[0]);
        builder.append(object.getClass().getName()).append('@').append(Integer.toHexString(System.identityHashCode(object)));
    }

    public static void identityToString(final StringBuffer buffer, final Object object) {
        notNull(object, "Cannot get the toString of a null identity", new Object[0]);
        buffer.append(object.getClass().getName()).append('@').append(Integer.toHexString(System.identityHashCode(object)));
    }

    public static void identityToString(final StringBuilder builder, final Object object) {
        notNull(object, "Cannot get the toString of a null identity", new Object[0]);
        builder.append(object.getClass().getName()).append('@').append(Integer.toHexString(System.identityHashCode(object)));
    }

    public static <T> T notNull(final T object, final String message, final Object... values) {
        if (object == null) {
            throw new NullPointerException(String.format(message, values));
        }
        return object;
    }

    public static void isTrue(final boolean expression, final String message, final long value) {
        if (!expression) {
            throw new IllegalArgumentException(String.format(message, value));
        }
    }

    public static void isTrue(final boolean expression, final String message, final double value) {
        if (!expression) {
            throw new IllegalArgumentException(String.format(message, value));
        }
    }

    public static void isTrue(final boolean expression, final String message, final Object... values) {
        if (!expression) {
            throw new IllegalArgumentException(String.format(message, values));
        }
    }

    public static void isTrue(final boolean expression) {
        if (!expression) {
            throw new IllegalArgumentException("The validated expression is false");
        }
    }

	public static char[] cloneArray(final char[] input) {
		return input.clone();
	}

	public static <T>List<List<T>> chopIntoParts(final List<T> ls, final int iParts) {
	    final List<List<T>> lsParts = new ArrayList<List<T>>();
	    final int iChunkSize = ls.size() / iParts;
	    int iLeftOver = ls.size() % iParts;
	    int iTake = iChunkSize;

	    for( int i = 0, iT = ls.size(); i < iT; i += iTake )
	    {
	        if( iLeftOver > 0 )
	        {
	            iLeftOver--;

	            iTake = iChunkSize + 1;
	        }
	        else
	        {
	            iTake = iChunkSize;
	        }

	        lsParts.add(new ArrayList<T>(ls.subList( i, Math.min( iT, i + iTake ))));
	    }

	    return lsParts;
	}

	public static <T> ArrayList<T> toArrayList(final T[] list) {
		if (list == null) {
			log.error("toArrayList(T[]) -> List given is null, returning nothing!");
			return new ArrayList<T>();
		}
		final ArrayList<T> out = new ArrayList<T>();
		for (final T it : list) {
			if (it != null) {
				out.add(it);
			}
		}
		return out;
	}

	public static <T> T[] add(final T[] items, final T item) {
		final ArrayList<T> out = new ArrayList<T>();
		for (final T it : items) {
			out.add(it);
		}
		out.add(item);
		return arrayListToArray(out);
	}

	public static <T> T[] addAll(final T[] items, final T[] items2) {
		final ArrayList<T> out = new ArrayList<T>();
		for (final T it : items) {
			out.add(it);
		}
		for (final T it : items2) {
			out.add(it);
		}
		return arrayListToArray(out);
	}

	public static <T> void comp(final T[] list1, T[] list2) {
		final ArrayList<T> a1 = toArrayList(list1);
		final ArrayList<T> a2 = toArrayList(list2);

		for (final T item : a1) {
			if (!a2.contains(item)) {
				System.out.println("List 2 does not contain '" + item + "'. [from list1]");
			}else {
				//System.out.println("List 2 does contains '" + item + "'. [from list1]");
			}
		}
		for (final T item : a2) {
			if (!a1.contains(item)) {
				System.out.println("List 1 does not contain '" + item + "'. [from list2]");
			}else {
				//System.out.println("List 1 does contains '" + item + "'. [from list2]");
			}
		}
	}

    public static <E> E[] arrayListToArray(ArrayList<E> list)
    {
        int s;
        if(list == null || (s = list.size())<1)
            return null;
        E[] temp;
        E typeHelper = list.get(0);

        int lastI = 0;
        try
        {
            Object o = Array.newInstance(typeHelper.getClass(), s);
            temp = (E[]) o;

            for (int i = 0; i < list.size(); i++) {
            	lastI = i;
            	if (list.get(i) != null) {
            		Array.<E>set(temp, i, list.get(i));
            	}
            }
                
        }
        catch (Exception e) {
        	//log.error("arrayListToArray(List<E>) -> Error on element " + lastI + "!", e);
        	
//        	try {
//        		return (E[]) list.toArray(new Object[list.size()]);
//        	}catch (Exception e1) {
//        		return (E[]) new Object[0];
//        	}
            Object[] temp1 = (Object[]) new Object[list.size()];
            for (int i = 0; i < list.size(); i++) {
            	try {
            		temp1[i] = list.get(i);
            	}catch (Exception ea) {
            		log.error("Error occured! [list=" + list + ",itemToAdd=" + list.get(i) + "]", ea);
            	}
            }
            return (E[]) temp1;
        }

        return temp;
    }
    
    public static <E> E[] listToArray(List<E> list)
    {
        int s;
        if(list == null || (s = list.size())<1)
            return null;
        E[] temp;
        E typeHelper = list.get(0);

        try
        {
            Object o = Array.newInstance(typeHelper.getClass(), s);
            temp = (E[]) o;

            for (int i = 0; i < list.size(); i++)
                Array.set(temp, i, list.get(i));
        }
        catch (Exception e) {
        	 Object[] temp1 = (Object[]) new Object[list.size()];
             for (int i = 0; i < list.size(); i++) {
             	try {
             		temp1[i] = list.get(i);
             	}catch (Exception ea) {
             		log.error("Error occured! [list=" + list + ",itemToAdd=" + list.get(i) + "]", ea);
             	}
             }
             return (E[]) temp1;
        }

        return temp;
    }
    
    public static <E> E[] linkedListToArray(LinkedList<E> list)
    {
        int s;
        if(list == null || (s = list.size())<1)
            return null;
        E[] temp;
        E typeHelper = list.get(0);

        try
        {
            Object o = Array.newInstance(typeHelper.getClass(), s);
            temp = (E[]) o;

            for (int i = 0; i < list.size(); i++)
                Array.set(temp, i, list.get(i));
        }
        catch (Exception e) {
        	Object[] temp1 = (Object[]) new Object[list.size()];
            for (int i = 0; i < list.size(); i++) {
            	try {
            		temp1[i] = list.get(i);
            	}catch (Exception ea) {
            		log.error("Error occured! [list=" + list + ",itemToAdd=" + list.get(i) + "]", ea);
            	}
            }
            return (E[]) temp1;
        }

        return temp;
    }

	public static <T> ArrayList<T> addMissing(final T[] list1, final T[] list2) {
		final ArrayList<T> out = new ArrayList<T>();
		final ArrayList<T> a1 = toArrayList(list1);
		final ArrayList<T> a2 = toArrayList(list2);

		for (final T item : a1) {
			if (!a2.contains(item)) {
				//System.out.println("List 2 does not contain '" + item + "'. [from list1]");
			}else {
				//System.out.println("List 2 does contains '" + item + "'. [from list1]");
			}
		}
		for (final T item : a2) {
			if (!a1.contains(item)) {
				out.add(item);
			}else {
				//System.out.println("List 1 does contains '" + item + "'. [from list2]");
			}
		}
		return out;
	}

	public static String[] clearBlanks(final String[] in) {
		final ArrayList<String> out = new ArrayList<String>();
		for (final String i : in) {
			if (i.length() > 1) {
				out.add(i);
			}
		}
		return arrayListToArray(out);
	}

	public static String[][] clearBlanksS(final String[][] in) {
		final ArrayList<String[]> out = new ArrayList<String[]>();
		for (final String[] i : in) {
			out.add(clearBlanks(i));
		}
		return arrayListToArray(out);
	}

	public static String[][] byLS(final String[] line) {
		final ArrayList<String[]> out = new ArrayList<String[]>();
		final ArrayList<String> temp = new ArrayList<String>();
		for (final String a : line) {
			final String li = a.replaceAll("\u0020", "");
			//System.out.println(li);
			if (li.length() == 0) {
				if (!temp.isEmpty()) {
					out.add(arrayListToArray(temp));
					temp.clear();
				}
			}else if (li != null) {
				temp.add(li);
			}
		}
		return arrayListToArray(out);
	}

	private static final String REGEX = "\\d";
	public static String[] splitByInt(final String in) {
		final ArrayList<String> out = new ArrayList<String>();
		final ArrayList<Integer> ints = new ArrayList<Integer>();
		final StringBuilder b = new StringBuilder();
		final char[] sf = in.toCharArray();
		for (int i = 0; i < sf.length; i++) {
			final char f = sf[i];
			if (Character.isDigit(f)) {
				ints.add(i);
			}
//				b.append(f);
//				out.add(b.toString());
//				b.delete(0, b.length());
//			}else {
//				b.append(f);
//			}
		}
		System.out.println(ints);
		return arrayListToArray(out);
	}

	public static String[][] byChar(final String[] data, final String s) {
		final ArrayList<String[]> out = new ArrayList<String[]>();
		final ArrayList<String> temp = new ArrayList<String>();
		for (final String dta : data) {
			if (!dta.equals(s)) {
				//System.out.println("Added '" + dta + "'.");
				temp.add(dta);
			}else if (dta.length() != 0){
				out.add(arrayListToArray(temp));
				temp.clear();
			}else {}
		}
		return arrayListToArray(out);
	}

	public static String bytePrint(final byte[] dta) {
		final StringBuilder data = new StringBuilder();
		for (final byte s : dta) {
			data.append(s);
		}
		return data.toString();
	}
	
	public static <E> Collection<E> makeCollection(Iterable<E> iter) {
	    Collection<E> list = new ArrayList<E>();
	    for (E item : iter) {
	        list.add(item);
	    }
	    return list;
	}

	public static <T> List<T> fromIterable(final Iterable<T> list) {
		return (List<T>) makeCollection(list);
	}

	public static void println(final String[] lines) {
		FastIO.get().println(Arrays.toString(lines));
	}
	
	public static <T> void println(final Set<T> lines) {
		String out = "[";
		int i = 0;
		for (final T item : lines) {
			if (i == lines.size() - 1) {
				out += item;
			}else {
				out += item + ", ";
			}
			i += 1;
		}
		out += "]";
		FastIO.get().println(out);
	}
	
	public static <T> void println(final List<T> lines) {
		FastIO.get().println(Arrays.toString(ArrayUtils.listToArray(lines)));
	}
	
	public static void println(final String pre_message, final String[] lines) {
		FastIO.get().println(pre_message + " | " + Arrays.toString(lines));
	}

//	public static void println(final Object[] item) {
//		if (item == null) {
//			return;
//		}
//		System.out.println(Arrays.toString(item));
//	}
	
	public static <T> void println(final T[] item) {
		if (item == null) {
			return;
		}
		FastIO.get().println(Arrays.toString(item));
	}

	public static void println(final byte[] item) {
		if (item == null) {
			return;
		}
		FastIO.get().println(Arrays.toString(item));
	}

	public static void printLln(final String[] lines) {
		if (lines == null) {
			return;
		}
		for (final String s : lines) {
			FastIO.get().println(s);
		}
	
	}
	
	public static <T> void printLln(final Enumeration<T> lines) {
		if (lines == null) {
			return;
		}
		while (lines.hasMoreElements()) {
			FastIO.get().println(lines.nextElement());
		}
	
	}
	
	public static void printLln(final AttributeSet lines) {
		if (lines == null) {
			return;
		}
		printLln(lines.toArray());
	}
	
	public static <T> void printLln(final Collection<T> lines) {
		if (lines == null) {
			return;
		}
		for (final T s : lines) {
			FastIO.get().println(s);
		}
	
	}
	
	public static <T> void printLln(final Set<T> lines) {
		if (lines == null) {
			return;
		}
		for (final T s : lines) {
			FastIO.get().println(s);
		}
	
	}
	
	public static <A, B> void printLln(final Map<A, B> lines) {
		if (lines == null) {
			return;
		}
		for (final Map.Entry<A, B> s : lines.entrySet()) {
			FastIO.get().println(s.getKey() + ": " + s.getValue());
		}
	
	}
	
	public static <T> void printLln(final T[] lines) {
		if (lines == null) {
			return;
		}
		for (final T s : lines) {
			System.out.println(s);
		}
	
	}
	
	public static <T> void printLln(final List<T> lines) {
		if (lines == null) {
			return;
		}
		for (final T s : lines) {
			//System.out.println(s);
			FastIO.get().println(s);
		}
	
	}
	
	public static void printLln_enc(final List<Encryptor> lines) {
		if (lines == null) {
			return;
		}
		for (final Encryptor s : lines) {
			FastIO.get().println(s.getEncryptorID());
		}
	
	}

	public static String toSList(final Long[] split) {
		final StringBuilder out = new StringBuilder();
		for (int i = 0; i < split.length; i++) {
			if (i < split.length - 1) {
				out.append(split[i] + ",");
			}else {
				out.append(split[i]);
			}
		}
		return out.toString();
	}
	
	public static <T> String toSList(final List<T> split) {
		final StringBuilder out = new StringBuilder();
		for (int i = 0; i < split.size(); i++) {
			if (i < split.size() - 1) {
				out.append(split.get(i) + ",");
			}else {
				out.append(split.get(i));
			}
		}
		return out.toString();
	}
	
	public static <T> String toSList(final T[] split) {
		if (split == null) {
			throw new UnsupportedOperationException("toSList(T[]) -> split was null!");
		}
		final StringBuilder out = new StringBuilder();
		for (int i = 0; i < split.length; i++) {
			if (i < split.length - 1) {
				out.append(split[i] + ",");
			}else {
				out.append(split[i]);
			}
		}
		return out.toString();
	}

	public static String toSList(final BigInteger[] split) {
		final StringBuilder out = new StringBuilder();
		for (int i = 0; i < split.length; i++) {
			if (i < split.length - 1) {
				out.append(split[i].toString() + ",");
			}else {
				out.append(split[i].toString());
			}
		}
		return out.toString();
	}

	public static void printLln(final ArrayList<String> list) {
		synchronized (System.err) {
			synchronized (System.out) {
				for (final String i : list) {
					System.out.println(i);
				}
			}
		}
	}

	public static <T> T[] from(final T[] data, final int from) {
		if (data == null) {
			return (T[]) new Object[0];
		}
		try {
			final ArrayList<T> out = new ArrayList<T>();
			for (int i = from; i < data.length; i++) {
				out.add(data[i]);
			}
			return arrayListToArray(out);
		}catch (Exception e) {
			e.printStackTrace();
			return (T[]) new Object[0];
		}
	}
	
	public static <T> T[] to(final T[] data, final int to) {
		if (data == null) {
			return (T[]) new Object[0];
		}
		try {
			final ArrayList<T> out = new ArrayList<T>();
			for (int i = 0; i < to; i++) {
				out.add(data[i]);
			}
			return arrayListToArray(out);
		}catch (Exception e) {
			e.printStackTrace();
			return (T[]) new Object[0];
		}
	}

	public static <T> T random(final T[] object) {
		return object[MathUtil.randInt(0, object.length - 1)];
	}
	
	public static <T> T random(final List<T> object) {
		return object.get(MathUtil.randInt(0, object.size() - 1));
	}
	
	public static char random(final char[] object) {
		return object[MathUtil.randInt(0, object.length - 1)];
	}

	public static int get(final String[] list, final String toFind) {
		for (int i = 0; i < list.length; i++) {
			final String x = list[i];
			if (x != null) {
				if (x.equals(toFind)) {
					return i;
				}
			}
		}
		return -1;
	}
	
	public static int get(final List<String> list, final String toFind) {
		for (int i = 0; i < list.size(); i++) {
			final String x = list.get(i);
			if (x != null) {
				if (x.equals(toFind)) {
					return i;
				}
			}
		}
		return -1;
	}

	public static <A, B> A indexOfA(final HashMap<A, B> map, final int index) {
		A out = null;
		
		int _index = 0;
		for (final Map.Entry<A, B> entry : map.entrySet()) {
			if (_index == index) {
				out = entry.getKey();
				break;
			}
			_index += 1;
		}
	
		return out;
	}

	public static <T> int get(final T[] list, final T toFind) {
		for (int i = 0; i < list.length; i++) {
			final T x = list[i];
			if (x != null) {
				if (x.equals(toFind)) {
					return i;
				}
			}
		}
		return -1;
	}

	public static ArrayList<Byte> toList(final byte[] data) {
		final ArrayList<Byte> out = new ArrayList<Byte>();
		for (final byte d : data) {
			out.add(d);
		}
		return out;
	}

	public static void printLln(final NodeList nodeList) {
		for (int i = 0; i < nodeList.getLength(); i++) {
			final Node n = nodeList.item(i);
			synchronized (System.err) {
				synchronized (System.out) {
					System.out.println(n.getNodeName() + ": [" + n.getTextContent() + "]");
					if (n.hasChildNodes()) {
						printLln(n.getChildNodes());
					}
				}
			}
		}
	}

	public static <T> T last(final T[] object) {
		if (object == null) {
			return null;
		}
		return object[object.length - 1];
	}
	
	public static <T> T last(final List<T> object) {
		if (object == null || object.isEmpty()) {
			return null;
		}
		return object.get(object.size() - 1);
	}
	
	public static <T> T fromArray(final T[] object, final int i) {
		if (object == null) {
			return null;
		}
		return object[object.length - 1 - i];
	}
	
	public static byte[][] create2D(int width, int height) {
        byte[][] result = new byte[height][];
        for (int i = 0; i < height; i++)
            result[i] = new byte[width];
        return result;
    }

	public static byte[] toByteArray(int[] ints) {
        byte[] result = new byte[ints.length];
        for (int i = 0; i < ints.length; i++)
            result[i] = (byte) ints[i];
        return result;
	}
	
	public static <T> T first(final T[] list) {
		return list[0];
	}

	public static <T> T first(final List<T> list) {
		return list.get(0);
	}

	public static Class<?>[] classes(final Object[] inputs) {
		final LinkedList<Class<?>> classes = new LinkedList<Class<?>>();
		for (final Object object : inputs) {
			classes.add(object.getClass());
		}
		return linkedListToArray(classes);
	}

	public static <T> String join(final T[] array, final String joinChar) {
		String s = "";
		for (int i = 0; i < array.length; i++) {
			if (i == array.length - 1) {
				s += array[i];
			}else {
				s += array[i];
				s += joinChar;
			}
		}
		return s;
	}

}
